package ru.pb.game.network.threading;

import java.util.Iterator;
import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.atomic.AtomicBoolean;

import org.apache.log4j.Logger;

import ru.pb.game.configs.GameThreadingConfig;
import ru.pb.game.network.clients.GameClient;
import ru.pb.game.network.engine.auth.packets.AuthServerToGameServerPacket;
import ru.pb.global.network.engine.NioClient;
import ru.pb.global.network.engine.core.emuns.ConnectionState;
import ru.pb.global.threading.RunnableTask;

/**
 * from l2f
 */
public class ContextPacketRunner implements PacketRunner
{
	private static final Logger _log = Logger.getLogger(ContextPacketRunner.class);

	private final AtomicBoolean _shutdown = new AtomicBoolean(false);

	private final Queue<AuthServerToGameServerPacket> _packets = new ConcurrentLinkedQueue<AuthServerToGameServerPacket>();

	private final GameClient[] _clients = new GameClient[GameThreadingConfig.PACKET_THREAD_POOL_SIZE];

	public ContextPacketRunner()
	{
		ThreadGroup threadGroup = new ThreadGroup("PB ContextPacketRunner");

		for(int i = 0; i < GameThreadingConfig.PACKET_THREAD_POOL_SIZE; i++)
		{
			Thread t = new Thread(threadGroup, new WorkingThread(), "PB ContextPacketRunner Thread - " + i);
			t.setPriority(DEFAULT_THREAD_PRIORITY);
			t.start();
		}
	}

	@Override
	public void runPacket(AuthServerToGameServerPacket packet)
	{
		if(_shutdown.get() || packet == null)
		{
			return;
		}

		NioClient client = packet.getClient();

		if(client == null || ConnectionState.DISCONNECTED == client.getState())
		{
			return;
		}

		synchronized(_packets)
		{
			_packets.add(packet);
		}
	}

	@Override
	public void removeContext(NioClient client)
	{
		synchronized(_packets)
		{
			Iterator<AuthServerToGameServerPacket> iterator = _packets.iterator();

			while(iterator.hasNext())
			{
				AuthServerToGameServerPacket info = iterator.next();
				if(info.getClient().equals(client))
				{
					iterator.remove();
				}
			}
		}
	}

	@Override
	public void shutdown()
	{
		_shutdown.set(true);
	}

	private AuthServerToGameServerPacket getPacketForRun()
	{
		synchronized(_packets)
		{
			Iterator<AuthServerToGameServerPacket> iterator = _packets.iterator();

			outer: while(iterator.hasNext())
			{
				AuthServerToGameServerPacket packet = iterator.next();
				NioClient client = packet.getClient();
				synchronized(_clients)
				{
					for(int i = 0, n = _clients.length; i < n; i++)
					{
						if(_clients[i] == client)
						{
							continue outer;
						}

						if(_clients[i] == null)
						{
							_clients[i] = (GameClient) client;
							iterator.remove();
							return packet;
						}
					}
				}
			}
		}

		return null;
	}

	private void unlockClient(NioClient client)
	{
		synchronized(_clients)
		{
			for(int i = 0, n = _clients.length; i < n; i++)
			{
				if(_clients[i] == client)
				{
					_clients[i] = null;
					return;
				}
			}
		}

		_log.info("Attempt to unlock not locked client.");
	}

	private class WorkingThread extends RunnableTask
	{
		@Override
		public void runImpl()
		{

			while( !_shutdown.get())
			{

				AuthServerToGameServerPacket packet = getPacketForRun();

				if(packet != null)
				{
					try
					{
						packet.run();
					}
					catch(Exception e)
					{
						_log.info("Uncaught exception: " + e, e);
					}
					finally
					{
						unlockClient(packet.getClient());
					}

				}
				else
				{
					try
					{
						Thread.sleep(5);
					}
					catch(InterruptedException e)
					{
						_log.info("Uncaught exception: " + e, e);
					}
				}
			}
		}
	}
}